﻿/******************************************************************************
	Cobalt - utility libraries for building XNA games
	Copyright (C) 2009 Philip Djonov

	This program is free software; you can redistribute it and/or modify it
	under the terms of the GNU General Public License as published by the Free
	Software Foundation; either version 2 of the License, or (at your option)
	any later version.

	This program is distributed in the hope that it will be useful, but
	WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
	or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
	for more details.

	You should have received a copy of the GNU General Public License along
	with this program; if not, write to the
	
		Free Software Foundation, Inc.
		51 Franklin Street, Fifth Floor
		Boston, MA  02110-1301, USA.

	Portions of this file are based on the Mozilla SHA1 implementation.
	See below for more information.
******************************************************************************/

using System;
using System.IO;
using System.Text;

namespace Cobalt
{

	/// <summary>
	/// A SHA1 hash of something.
	/// </summary>
	public struct Hash
	{
		/// <summary>
		/// The hash contents.
		/// </summary>
		public uint A, B, C, D, E;

		/// <summary>
		/// Initializes a new hash.
		/// </summary>
		/// <param name="a">The first four bytes of the hash.</param>
		/// <param name="b">The second four bytes of the hash.</param>
		/// <param name="c">The third four bytes of the hash.</param>
		/// <param name="d">The fourth four bytes of the hash.</param>
		/// <param name="e">The fifth four bytes of the hash.</param>
		public Hash( uint a, uint b, uint c, uint d, uint e )
		{
			A = a;
			B = b;
			C = c;
			D = d;
			E = e;
		}

		/// <summary>
		/// Compares the hash against another for equality.
		/// </summary>
		/// <param name="other">The hash to compare to.</param>
		/// <returns>True if this hash equals <paramref name="other"/>.</returns>
		public bool Equals( Hash other )
		{
			return A == other.A && B == other.B && C == other.C && D == other.D && E == other.E;
		}

		/// <summary>
		/// Compares the hash against another for equality.
		/// </summary>
		/// <param name="obj">The hash to compare to.</param>
		/// <returns>True if this hash equals <paramref name="obj"/>.</returns>
		public override bool Equals( object obj )
		{
			if( obj == null || obj.GetType() != typeof( Hash ) )
				return false;

			return Equals( (Hash)obj );
		}

		/// <summary>
		/// Returns a 32-bit hash of the full hash.
		/// </summary>
		/// <returns>A 32-bit hash of the full thing.</returns>
		public override int GetHashCode()
		{
			return unchecked( (int)A );
		}

		static readonly char[] HexChars = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };

		private static void IntToHex( StringBuilder dest, uint n )
		{
			dest.Append( HexChars[(n >> 28) & 0x0F] );
			dest.Append( HexChars[(n >> 24) & 0x0F] );
			dest.Append( HexChars[(n >> 20) & 0x0F] );
			dest.Append( HexChars[(n >> 16) & 0x0F] );
			dest.Append( HexChars[(n >> 12) & 0x0F] );
			dest.Append( HexChars[(n >> 8) & 0x0F] );
			dest.Append( HexChars[(n >> 4) & 0x0F] );
			dest.Append( HexChars[(n >> 0) & 0x0F] );
		}

		/// <summary>
		/// Converts the hash to a string.
		/// </summary>
		/// <returns>A 40-byte string containing the hash value.</returns>
		public override string ToString()
		{
			StringBuilder ret = new StringBuilder();

			IntToHex( ret, A );
			IntToHex( ret, B );
			IntToHex( ret, C );
			IntToHex( ret, D );
			IntToHex( ret, E );

			return ret.ToString();
		}

		public byte[] GetHashBytes()
		{
			byte[] ret = new byte[20];

			ret[0] = (byte)((A >> 24) & 0xFF);
			ret[1] = (byte)((A >> 16) & 0xFF);
			ret[2] = (byte)((A >> 8) & 0xFF);
			ret[3] = (byte)((A >> 0) & 0xFF);

			ret[4] = (byte)((B >> 24) & 0xFF);
			ret[5] = (byte)((B >> 16) & 0xFF);
			ret[6] = (byte)((B >> 8) & 0xFF);
			ret[7] = (byte)((B >> 0) & 0xFF);

			ret[8] = (byte)((C >> 24) & 0xFF);
			ret[9] = (byte)((C >> 16) & 0xFF);
			ret[10] = (byte)((C >> 8) & 0xFF);
			ret[11] = (byte)((C >> 0) & 0xFF);

			ret[12] = (byte)((D >> 24) & 0xFF);
			ret[13] = (byte)((D >> 16) & 0xFF);
			ret[14] = (byte)((D >> 8) & 0xFF);
			ret[15] = (byte)((D >> 0) & 0xFF);

			ret[16] = (byte)((E >> 24) & 0xFF);
			ret[17] = (byte)((E >> 16) & 0xFF);
			ret[18] = (byte)((E >> 8) & 0xFF);
			ret[19] = (byte)((E >> 0) & 0xFF);

			return ret;
		}

		private static uint ParseHex( string text, int index )
		{
			uint ret = 0;

			for( int i = 0; i < 8; i++ )
			{
				char ch = text[index + i];

				if( ch >= '0' && ch <= '9' )
				{
					ret |= (uint)(ch - '0') << (32 - 4 * (i + 1));
				}
				else if( ch >= 'A' && ch <= 'F' )
				{
					ret |= (uint)(ch - 'A') << (32 - 4 * (i + 1));
				}
				else if( ch >= 'a' && ch <= 'f' )
				{
					ret |= (uint)(ch - 'a') << (32 - 4 * (i + 1));
				}
				else
					throw new FormatException( "Non hexadecimal character in input string." );
			}

			return ret;
		}

		/// <summary>
		/// Parses a 40-byte hexadecimal representation of a hash.
		/// </summary>
		/// <param name="text">The string containing the hash value.</param>
		/// <param name="index">The index to start parsing at.</param>
		/// <exception cref="ArgumentNullException">
		/// <paramref name="text"/> is <c>null</c>.
		/// </exception>
		/// <exception cref="ArgumentOutOfRangeException">
		/// <paramref name="index"/> does not index a 40-character span in the string.
		/// </exception>
		/// <exception cref="FormatException">The string contains non-hexadecimal characters.</exception>
		/// <returns>The parsed hash</returns>
		public static Hash Parse( string text, int index )
		{
			if( text == null )
				throw new ArgumentNullException( "text" );
			if( index < 0 || index + 40 > text.Length )
				throw new ArgumentOutOfRangeException( "index" );

			Hash ret;

			ret.A = ParseHex( text, index + 0 );
			ret.B = ParseHex( text, index + 8 );
			ret.C = ParseHex( text, index + 16 );
			ret.D = ParseHex( text, index + 24 );
			ret.E = ParseHex( text, index + 32 );

			return ret;
		}
	}

	/*
	 * NOTICE:
	 *		What follows is based on the Mozilla SHA1 source. It is
	 *		included here under the GPL license option.
	 * 
	 * GPL PORTION OF ORIGINAL NOTICE FOLLOWS:
	 * 
	 * The Initial Developer of the Original Code is Paul Kocher of
	 * Cryptography Research.  Portions created by Paul Kocher are
	 * Copyright (C) 1995-9 by Cryptography Research, Inc.  All
	 * Rights Reserved.
	 *
	 * Contributor(s):
	 *
	 *     Paul Kocher
	 *
	 * Alternatively, the contents of this file may be used under the
	 * terms of the GNU General Public License Version 2 or later (the
	 * "GPL"), in which case the provisions of the GPL are applicable
	 * instead of those above.  If you wish to allow use of your
	 * version of this file only under the terms of the GPL and not to
	 * allow others to use your version of this file under the MPL,
	 * indicate your decision by deleting the provisions above and
	 * replace them with the notice and other provisions required by
	 * the GPL.  If you do not delete the provisions above, a recipient
	 * may use your version of this file under either the MPL or the
	 * GPL.
	 */

	/// <summary>
	/// An implementation of the SHA1 hashing algorithm.
	/// </summary>
	public sealed class HashContext
	{
		private uint[] H = new uint[5];
		private uint[] W = new uint[80];
		private int lenW;
		private uint sizeHi, sizeLo;

		/// <summary>
		/// Initializes a new <see cref="HashContext"/>.
		/// </summary>
		/// <remarks>
		/// The constructor calls <see cref="Initialize"/>, there is
		/// no need to call this function a second time.
		/// </remarks>
		public HashContext()
		{
			Begin();
		}

		/// <summary>
		/// Discards the current state and reinitializes the <see cref="HashContext"/>.
		/// </summary>
		/// <remarks>
		/// There is no need to call this method on freshly created instances as the
		/// constructor will already have done so.
		/// </remarks>
		public void Begin()
		{
			lenW = 0;
			sizeHi = 0;
			sizeLo = 0;

			//Initialize H with the magic constants (see FIPS180 for constants)
			H[0] = 0x67452301;
			H[1] = 0xefcdab89;
			H[2] = 0x98badcfe;
			H[3] = 0x10325476;
			H[4] = 0xc3d2e1f0;

			Array.Clear( W, 0, W.Length );
		}

		/// <summary>
		/// Adds a span of bytes to the current hash computation.
		/// </summary>
		/// <param name="data">Tye bytes to hash.</param>
		/// <param name="index">The index in the array to start hashing at.</param>
		/// <param name="length">The number of bytes to hash.</param>
		public void Update( byte[] data, int index, int length )
		{
			if( data == null )
				throw new ArgumentNullException( "data" );
			if( index < 0 || length < 0 || index + length > data.Length )
				throw new IndexOutOfRangeException();

			//Read the data into W and process blocks as they get full

			int end = index + length;
			for( int i = index; i < end; i++ )
			{
				W[lenW / 4] = (W[lenW / 4] << 8) | (uint)data[i];

				if( ++lenW % 64 == 0 )
				{
					HashBlock();
					lenW = 0;
				}

				unchecked
				{
					sizeLo += 8;
					sizeHi += sizeLo < 8 ? 1U : 0U;
				}
			}
		}

		/// <summary>
		/// Adds a span of bytes to the current hash computation.
		/// </summary>
		/// <param name="data">Tye bytes to hash.</param>
		public void Update( byte[] data )
		{
			if( data == null )
				throw new ArgumentNullException( "data" );

			Update( data, 0, data.Length );
		}

		/// <summary>
		/// Adds a stream of bytes to the current hash computation.
		/// </summary>
		/// <param name="stream">The stream to hash.</param>
		public void Update( Stream stream )
		{
			if( stream == null )
				throw new ArgumentNullException( "data" );

			//Read the data into W and process blocks as they get full

			for( int b = stream.ReadByte(); b != -1; b = stream.ReadByte() )
			{
				W[lenW / 4] = (W[lenW / 4] << 8) | (uint)b;

				if( ++lenW % 64 == 0 )
				{
					HashBlock();
					lenW = 0;
				}

				unchecked
				{
					sizeLo += 8;
					sizeHi += sizeLo < 8 ? 1U : 0U;
				}
			}
		}

		/// <summary>
		/// Adds a string to the current hash computation.
		/// </summary>
		/// <param name="text">Tye string to hash.</param>
		/// <param name="index">The index in the string to start hashing at.</param>
		/// <param name="length">The number of characters to hash.</param>
		public void Update( string text, int index, int length )
		{
			if( text == null )
				throw new ArgumentNullException( "data" );
			if( index < 0 || length < 0 || index + length > text.Length )
				throw new IndexOutOfRangeException();

			//Read the data into W and process blocks as they get full

			int end = index + length;
			for( int i = index; i < end; i++ )
			{
				W[lenW / 4] = (W[lenW / 4] << 8) | ((uint)text[i] >> 8);

				if( ++lenW % 64 == 0 )
				{
					HashBlock();
					lenW = 0;
				}

				unchecked
				{
					sizeLo += 8;
					sizeHi += sizeLo < 8 ? 1U : 0U;
				}

				W[lenW / 4] = (W[lenW / 4] << 8) | ((uint)text[i] & 0xFF);

				if( ++lenW % 64 == 0 )
				{
					HashBlock();
					lenW = 0;
				}

				unchecked
				{
					sizeLo += 8;
					sizeHi += sizeLo < 8 ? 1U : 0U;
				}

			}
		}

		/// <summary>
		/// Adds a string to the current hash computation.
		/// </summary>
		/// <param name="text">Tye string to hash.</param>
		public void Update( string text )
		{
			if( text == null )
				throw new ArgumentNullException( "data" );
			Update( text, 0, text.Length );
		}

		/// <summary>
		/// Adds a string to the current hash computation.
		/// </summary>
		/// <param name="text">Tye string to hash.</param>
		/// <param name="index">The index in the string to start hashing at.</param>
		/// <param name="length">The number of characters to hash.</param>
		public void Update( StringBuilder text, int index, int length )
		{
			if( text == null )
				throw new ArgumentNullException( "data" );
			if( index < 0 || length < 0 || index + length > text.Length )
				throw new IndexOutOfRangeException();

			//Read the data into W and process blocks as they get full

			int end = index + length;
			for( int i = index; i < end; i++ )
			{
				W[lenW / 4] = (W[lenW / 4] << 8) | ((uint)text[i] >> 8);

				if( ++lenW % 64 == 0 )
				{
					HashBlock();
					lenW = 0;
				}

				unchecked
				{
					sizeLo += 8;
					sizeHi += sizeLo < 8 ? 1U : 0U;
				}

				W[lenW / 4] = (W[lenW / 4] << 8) | ((uint)text[i] & 0xFF);

				if( ++lenW % 64 == 0 )
				{
					HashBlock();
					lenW = 0;
				}

				unchecked
				{
					sizeLo += 8;
					sizeHi += sizeLo < 8 ? 1U : 0U;
				}

			}
		}

		/// <summary>
		/// Adds a string to the current hash computation.
		/// </summary>
		/// <param name="text">Tye string to hash.</param>
		public void Update( StringBuilder text )
		{
			if( text == null )
				throw new ArgumentNullException( "data" );

			Update( text, 0, text.Length );
		}

		private static uint ShaRot( uint x, int n )
		{
			return unchecked( (x << n) | (x >> (32 - n)) );
		}

		private void HashBlock()
		{
			int t;
			uint A,B,C,D,E,TEMP;

			for( t = 16; t < 80; t++ )
				W[t] = ShaRot( W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16], 1 );

			A = H[0];
			B = H[1];
			C = H[2];
			D = H[3];
			E = H[4];

			for( t = 0; t < 20; t++ )
			{
				TEMP = ShaRot( A, 5 ) + (((C ^ D) & B) ^ D) + E + W[t] + 0x5a827999;
				E = D;
				D = C;
				C = ShaRot( B, 30 );
				B = A;
				A = TEMP;
			}

			for( t = 20; t < 40; t++ )
			{
				TEMP = ShaRot( A, 5 ) + (B ^ C ^ D) + E + W[t] + 0x6ed9eba1;
				E = D;
				D = C;
				C = ShaRot( B, 30 );
				B = A;
				A = TEMP;
			}

			for( t = 40; t < 60; t++ )
			{
				TEMP = ShaRot( A, 5 ) + ((B & C) | (D & (B | C))) + E + W[t] + 0x8f1bbcdc;
				E = D;
				D = C;
				C = ShaRot( B, 30 );
				B = A;
				A = TEMP;
			}

			for( t = 60; t < 80; t++ )
			{
				TEMP = ShaRot( A, 5 ) + (B ^ C ^ D) + E + W[t] + 0xca62c1d6;
				E = D;
				D = C;
				C = ShaRot( B, 30 );
				B = A;
				A = TEMP;
			}

			H[0] += A;
			H[1] += B;
			H[2] += C;
			H[3] += D;
			H[4] += E;
		}

		private byte[] paddingBuf = new byte[10];

		/// <summary>
		/// Finishes computing the hash and reinitializes the context.
		/// </summary>
		/// <returns>
		/// A <see cref="Hash"/> of the data fed to the context through
		/// <see cref="M:Update"/> since the last call to <see cref="Begin"/>.
		/// </returns>
		/// <remarks>
		/// This method resets the context by calling <see cref="Begin"/>.
		/// There is no need to do so again after calling this.
		/// </remarks>
		public Hash End()
		{
			//pad with a binary one
			paddingBuf[8] = 0x80;
			Update( paddingBuf, 8, 1 );

			//and then a bunch of zeroes
			while( lenW != 56 )
				Update( paddingBuf, 9, 1 );

			//and then length
			paddingBuf[0] = (byte)((sizeHi >> 24) & 0xFF);
			paddingBuf[1] = (byte)((sizeHi >> 16) & 0xFF);
			paddingBuf[2] = (byte)((sizeHi >> 8) & 0xFF);
			paddingBuf[3] = (byte)((sizeHi >> 0) & 0xFF);
			paddingBuf[4] = (byte)((sizeLo >> 24) & 0xFF);
			paddingBuf[5] = (byte)((sizeLo >> 16) & 0xFF);
			paddingBuf[6] = (byte)((sizeLo >> 8) & 0xFF);
			paddingBuf[7] = (byte)((sizeLo >> 0) & 0xFF);
			Update( paddingBuf, 0, 8 );

			//capture the result
			Hash ret = new Hash( H[0], H[1], H[2], H[3], H[4] );

			//reset for another run
			Begin();

			return ret;
		}
	}


}